package com.example.security.service;

import com.example.security.config.SsoClientProperties;
import com.example.security.entity.SysUser;
import com.example.security.exception.InvalidCredentialsException;
import com.example.security.exception.InvalidTicketException;
import com.example.security.sso.SsoClient;
import com.example.security.util.JwtTokenProvider;
import com.example.security.vo.LoginRequest;
import com.example.security.vo.LoginResponse;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.scheduling.annotation.Async;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;

import java.time.LocalDateTime;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

/**
 * SSO服务
 */
@Slf4j
@Service
@RequiredArgsConstructor
public class SsoService {
    
    private final RedisTemplate<String, Object> redisTemplate;
    private final RestTemplate restTemplate;
    private final JwtTokenProvider tokenProvider;
    private final UserService userService;
    private final SsoClientService ssoClientService;
    private final SsoClientProperties ssoProperties;
    private final PasswordEncoder passwordEncoder;
    @Autowired
    private UserDetailsService userDetailsService ;
    
    /**
     * 生成SSO票据
     */
    public String generateTicket(String token) {
        String ticket = UUID.randomUUID().toString();
        redisTemplate.opsForValue().set(
            "sso:ticket:" + ticket,
            token,
            5,
            TimeUnit.MINUTES
        );
        return ticket;
    }
    
    /**
     * 验证SSO票据
     */
    public String validateTicket(String ticket, String clientId) {
        String token = (String) redisTemplate.opsForValue().get("sso:ticket:" + ticket);
        if (token == null) {
            throw new InvalidTicketException("无效的票据");
        }
        
        String username = tokenProvider.getUsernameFromToken(token);
        recordClientSession(username, clientId);
        
        redisTemplate.delete("sso:ticket:" + ticket);
        return token;
    }
    
    /**
     * 记录客户端会话
     */
    private void recordClientSession(String username, String clientId) {
        String sessionKey = "sso:session:" + username;
        redisTemplate.opsForHash().put(
            sessionKey,
            clientId,
            LocalDateTime.now()
        );
    }
    
    /**
     * SSO登录
     */
    public LoginResponse login(LoginRequest request) {
        // 获取用户信息
        SysUser user = userService.getByUserId(request.getUserId());
        if (user == null) {
            throw new InvalidCredentialsException("用户名或密码错误");
        }
        if (!userService.isAdmin(user)) {
            throw new InvalidCredentialsException("用户名或密码错误");
        }
        UserDetails userDetails = userDetailsService.loadUserByUsername(user.getUsername());
        
        // 验证密码
        if (!passwordEncoder.matches(request.getPassword(), userDetails.getPassword())) {
            throw new InvalidCredentialsException("用户名或密码错误");
        }
        
        // 生成token
        String token = tokenProvider.generateToken(userDetails);
        
        // 记录会话
        recordClientSession(userDetails.getUsername(), request.getClientId());
        
        return LoginResponse.builder()
            .token(token)
            .user(userService.getByUserId(request.getUserId()))
            .permissions(userService.getUserPermissions(request.getUserId()))
            .expireTime(ssoProperties.getToken().getExpireTime())
            .build();
    }
    
    /**
     * 注销登录
     */
    public void logout(String token) {
        if (token != null && token.startsWith("Bearer ")) {
            token = token.substring(7);
        }
        
        String username = tokenProvider.getUsernameFromToken(token);
        if (username != null) {
            notifyLogout(username, null);
        }
    }
    
    /**
     * 通知其他客户端注销
     */
    @Async
    public void notifyLogout(String username, String currentClientId) {
        try {
            Map<Object, Object> sessions = redisTemplate.opsForHash()
                .entries("sso:session:" + username);
                
            sessions.forEach((clientId, lastAccess) -> {
                if (!clientId.equals(currentClientId)) {
                    try {
                        SsoClient client = ssoClientService.getClient(clientId.toString());
                        if (client != null) {
                            notifyClientLogout(client.getClientUrl(), username);
                        }
                    } catch (Exception e) {
                        log.error("通知客户端登出失败: {}", clientId, e);
                    }
                }
            });
            
            redisTemplate.delete("sso:session:" + username);
        } catch (Exception e) {
            log.error("SSO登出同步失败", e);
        }
    }
    
    /**
     * 通知客户端注销
     */
    private void notifyClientLogout(String clientUrl, String username) {
        try {
            String logoutUrl = clientUrl + "/api/sso/logout";
            
            HttpHeaders headers = new HttpHeaders();
            headers.setContentType(MediaType.APPLICATION_JSON);
            
            Map<String, String> body = new HashMap<>();
            body.put("username", username);
            
            HttpEntity<Map<String, String>> request = new HttpEntity<>(body, headers);
            
            restTemplate.postForEntity(logoutUrl, request, Void.class);
            log.debug("通知客户端注销成功: url={}, username={}", logoutUrl, username);
        } catch (Exception e) {
            log.error("通知客户端注销失败: url=" + clientUrl, e);
        }
    }
} 